package tx.oopa.circuit.view;

import java.awt.Container;
import java.awt.Point;
import java.util.List;
import java.util.Random;

import javax.swing.JComponent;

import tx.oopa.circuit.node.AbstractEdgingNode;
import tx.oopa.circuit.node.AbstractNode;
import tx.oopa.circuit.node.StartingNode;

public class NodeVisualizer {

    private static final Random RANDOM = new Random(System.currentTimeMillis());

    public static int MAX_X = 0;
    public static int MAX_Y = 0;

    private NodeVisualizer() {

    }

    /**
     * Visualizes the given collection of nodes on a graphical component.
     * 
     * @param nodes
     *            The nodes to visualize.
     * @param container
     *            The container to draw nodes on.
     * @return The given container with a visualization of the given nodes.
     */
    public static final Container visualizeNodes(List<AbstractNode> nodes) {
        if(nodes == null) {
            throw new IllegalArgumentException("Nodes cannot be null.");
        }

        // Create base canvas
        JComponent nodeCanvas = new NodeCanvas();

        StartingNode previousStartingNode = null;

        // Determine position for all nodes
        for(AbstractNode node : nodes) {
            if(node instanceof StartingNode) {
                Point startPoint = new Point(10, 10);
                if(previousStartingNode != null) {
                    startPoint.y += NodeComponent.ACTUAL_HEIGHT * nodes.size()
                            / 2;
                }
                NodeComponentDecorator.MAP.put(node, startPoint);
                positionNextNodes((AbstractEdgingNode) node);
                previousStartingNode = (StartingNode) node;
            }
        }

        // Decorate
        JComponent canvas = new NodeDecorator(new NodeLineDecorator(nodeCanvas));

        return canvas;
    }

    /**
     * Positions all next nodes for the given node recursively.
     * 
     * @param node
     */
    private static final void positionNextNodes(AbstractEdgingNode node) {
        if(node == null) {
            return;
        }
        // Loop all edging nodes
        for(AbstractNode nextNode : node.getEdgingNodes()) {
            if(!NodeComponentDecorator.MAP.containsKey(nextNode)) {
                // Get point from previous node
                Point previousPoint = NodeComponentDecorator.MAP.get(node);
                // Determine point for next node
                Point nextPoint = null;

                if(!(nextNode instanceof AbstractEdgingNode)) {
                    // Not an edging node
                    nextPoint = new Point(previousPoint.x + 2
                            * NodeComponent.ACTUAL_WIDTH, previousPoint.y);
                    NodeComponentDecorator.MAP.put(nextNode, nextPoint);
                }
                else {
                    // Continue if more edging nodes are available
                    int nextNodeCount = ((AbstractEdgingNode) nextNode)
                            .getEdgingNodes().size();
                    nextPoint = new Point(
                            (int) (previousPoint.x + NodeComponent.ACTUAL_WIDTH
                                    * nextNodeCount * (RANDOM.nextDouble() * 5)),
                            (int) (previousPoint.y + NodeComponent.ACTUAL_HEIGHT
                                    * nextNodeCount * (RANDOM.nextDouble() * 4)));
                    applyMargins(nextPoint);
                    NodeComponentDecorator.MAP.put(nextNode, nextPoint);
                    positionNextNodes((AbstractEdgingNode) nextNode);
                }

                if(nextPoint.x > MAX_X) {
                    MAX_X = nextPoint.x + NodeComponent.ACTUAL_WIDTH * 2;
                }
                if(nextPoint.y > MAX_Y) {
                    MAX_Y = nextPoint.y + NodeComponent.ACTUAL_HEIGHT * 2;
                }
            }
        }
    }
    
    private static void applyMargins(Point p) {
        if(p == null) {
            throw new IllegalArgumentException("Point cannot be null");
        }
        for(Point existingPoint : NodeComponentDecorator.MAP.values()) {
            // Check margins for point
            if(p.x <= existingPoint.x + NodeComponent.ACTUAL_WIDTH) {
                p.x += NodeComponent.ACTUAL_WIDTH * 2;
            }
            if(p.y <= existingPoint.y + NodeComponent.ACTUAL_HEIGHT) {
                p.y += NodeComponent.ACTUAL_HEIGHT * 2;
            }
        }
        
        
    }
}
